#include <iostream>
#include <thread>
#include <memory>
#include "../example/test1.service.Service.proxy.h"
#include "../example/test1.service.ServiceDynamic.proxy.h"
#include "../rpc_root.h"
#include "unittest.hh"
#include "../rpc_defines.h"

// Simulates a user implemented Transport interface
class TransportImpl : public rpc::Transport {
    char* data_ {nullptr};
    int begin_ {0};
    int end_ {0};
    bool close_ {false};

public:
    TransportImpl() {
        data_ = new char[1024 * 16];
    }

    virtual ~TransportImpl() {
        delete[] data_;
    }

    virtual int send(const char* data, int size) override {
        begin_ = 0;
        end_ = 0;
        memcpy(data_ + begin_, data, size);
        end_ += size;
        return size;
    }
    virtual int peek(char* data, int size) override {
        memcpy(data, data_ + begin_, size);
        return size;
    }
    virtual int recv(char* data, int size) override {
        memcpy(data, data_ + begin_, size);
        begin_ += size;
        if (begin_ == end_) {
            begin_ = 0;
            end_ = 0;
        }
        return size;
    }
    virtual int skip(int size) override {
        begin_ += size;
        return size;
    }
    virtual int size() override {
        return end_ - begin_;
    }
    virtual bool isClose() override {
        return close_;
    }
    virtual void close() override {
        close_ = true;
    }
};

auto get_os_time_millionsecond()->std::time_t {
  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::system_clock::now().time_since_epoch())
    .count();
  return ms;
}

FIXTURE_BEGIN(RPC)

CASE(TESTOnewayMethod) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  service->method1({}, "123");
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
}

CASE(TestCoroutineProxyMethod) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::createProxy<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  coro_start([&](void*) {
    auto ret = service->method2(1, { "2", "3" }, 4);
    flag = (ret == "hello");
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}

CASE(TestAsyncProxyMethod) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  service->method2(1, { "2", "3" }, 4, [&](const std::string& ret, rpc::RpcError) {
    flag = (ret == "hello");
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}

CASE(TestAsyncProxyMethodException1) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  service->method4([&](rpc::RpcError error) {
    flag = (error == rpc::RpcError::EXCEPTION);;
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}


CASE(TestCoroutineProxyMethodException2) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  coro_start([&](void*) {
    try {
      service->method4();
    }
    catch (rpc::RemoteMethodException&) {
      flag = true;
    }
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}

CASE(TestProxyMethodTimeout) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();

  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  co(
    try {
    service->method4();
  }
  catch (rpc::RemoteMethodTimeout&) {
    flag = true;
  }
  )
  std::this_thread::sleep_for(std::chrono::seconds(6));
  rpc->update();
  ASSERT_TRUE(flag);
}

CASE(TestProxyMethodMainCoroutine) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  try {
    service->method5();
  }
  catch (rpc::ProxyCoroMethodException&) {
    flag = true;
  }
  ASSERT_TRUE(flag);
}

CASE(TestOnewayMethodDynamic) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  auto trans = std::make_shared<TransportImpl>();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  service->method1({}, "123");
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
}

CASE(TestCoroutineProxyMethodDynamic) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  co(
    auto ret = service->method2(1, { "2", "3" }, 4);
  flag = (ret == "hello");
  );
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}

CASE(TestCoroutineProxyMethodAsyncDynamic) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  service->method2(1, { "2", "3" }, 4, [&](const std::string& ret, rpc::RpcError error) {
    flag = (ret == "hello") && (error == rpc::RpcError::OK);
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}

CASE(TestProxyMethodExceptionDynamic1) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  service->method4([&](rpc::RpcError error) {
    flag = (error == rpc::RpcError::EXCEPTION);
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}


CASE(TestProxyMethodExceptionDynamic2) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  co(
    try {
    service->method4();
  }
  catch (rpc::RemoteMethodException&) {
    flag = true;
  }
  )
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag);
}


CASE(TestProxyMethodTimeoutDynamic) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  co(
    try {
    service->method4();
  }
  catch (rpc::RemoteMethodTimeout&) {
    flag = true;
  }
  )
  std::this_thread::sleep_for(std::chrono::seconds(6));
  rpc->update();
  ASSERT_TRUE(flag);
}

CASE(TestProxyMethodMainCoroutineDynamic) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  try {
    service->method5();
  }
  catch (rpc::ProxyCoroMethodException&) {
    flag = true;
  }
  ASSERT_TRUE(flag);
}

CASE(TestDynamicReload1) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag1 = false;
  co(
    auto ret = service->method2(1, { "2", "3" }, 4);
  flag1 = (ret == "hello");
  );
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag1);
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic2.dll");
  rpc->update();
  bool flag2 = false;
  coro_start([&](void*) {
    auto ret = service->method2(1, { "2", "3" }, 4);
    flag2 = (ret == "hello world");
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag2);
}

CASE(TestDynamicReload2) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic2.dll");
  rpc::unloadClassUnsafe(rpc, "9046620621990182746");
  auto trans = std::make_shared<TransportImpl>();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag1 = false;
  coro_start([&](void*) {
    auto ret = service->method2(1, { "2", "3" }, 4);
  flag1 = (ret == "hello");
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic2.dll");
  rpc->update();
  bool flag2 = false;
  coro_start([&](void*) {
    auto ret = service->method2(1, { "2", "3" }, 4);
    flag2 = (ret == "hello world");
    });
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  ASSERT_TRUE(flag1);
  ASSERT_TRUE(flag2);
}

CASE(TestOnewayMethodDynamicNotLoad) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic2.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  service->method1({}, "123");
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
}

CASE(TestOnewayMethodDynamicLoadWrongUUID) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic3.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  service->method1({}, "123");
  rpc->onMessage(std::dynamic_pointer_cast<rpc::Transport>(trans));
  rpc->update();
}

CASE(TestMethodRetry1) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  coro_start([&](void*) {
    try {
      service->method2(1, {}, 2);
    }
    catch (rpc::RemoteMethodTimeout&) {
      flag = true;
    }
  });
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(!flag);
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(!flag);
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(!flag);
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(flag);
}

CASE(TestMethodRetry2) {
  auto* rpc = new rpc::RpcImpl();
  rpc->initialize();
  rpc::loadClass(rpc, "9046620621990182746", "ServiceDynamic.dll");
  auto trans = std::make_shared<TransportImpl>();
  auto service = rpc::getService<ServiceDynamicProxy>(std::dynamic_pointer_cast<rpc::Transport>(trans), true, rpc);
  bool flag = false;
  service->method2(1, {}, 2, [&](const std::string&, rpc::RpcError error) {
      flag = (error == rpc::RpcError::TIMEOUT);
  });
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(!flag);
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(!flag);
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(!flag);
  std::this_thread::sleep_for(std::chrono::seconds(3));
  rpc->update();
  ASSERT_TRUE(flag);
}

TEARDOWN([](){
    rpc::deinitialize();
})

FIXTURE_END(RPC)
